#pragma once

#include	<Network.h>
#include	<Logger.h>
#include	<google/protobuf/arena.h>
#include	<google/protobuf/message.h>

#if defined(_WIN32)
#	pragma	warning(disable:4200)
#endif

#pragma pack(push, 1)

/**
 * Protocol definition.
 */
struct Protocol {
	uint16_t	pf;			//! Protocol family.
	uint16_t	size;		//! Size of this message includes header.
	uint32_t	serial;		//! Serial number. Used to prevent resend & handle response.
	char		data[0];	//! Real data.
};

#pragma pack(pop)

/**
 * Wrapper for Connection. Used by TCP server side.
 */
class Sender {
public:
	Sender(Connection * conn) : _conn(conn) { _conn->Attach(this); }
	virtual ~Sender() { _conn->Attach(nullptr); }

	/**
	 * Get IP address of client.
	 */
	inline const char * IP() const { return _conn->IP(); }

	/**
	 * Get unique id of this client.
	 */
	inline uint64_t Id() const { return _conn->Id(); }

	/**
	 * Close this connection.
	 */
	inline void Close() { return _conn->Close(); }

	/**
	 * Attach user data to this client.
	 *
	 * \param	userdata	Custom user data.
	 */
	inline void Attach(void * userdata) { _userdata = userdata; }

	/**
	 * Get attached user data.
	 */
	template<typename T>
	T * Attached() { return _userdata ? (T *)_userdata : nullptr; }

	/**
	 * Send google::protobuf::Message back to client.
	 *
	 * \param	pf			Protocol family.
	 * \param	msg			Real message.
	 * \param	serial		Serial number. Default is 0
	 * \param	threadsafe	Should use thread safe buffer? Default is false.
	 * \return Whether this message has been sent to client successfully?
	 */
	bool Reply(int pf, const google::protobuf::Message & msg, uint32_t serial = 0, bool threadsafe = false);

private:
	Connection * _conn;
	void * _userdata;
};

/**
 * TCP client side base.
 */
class IBaseConnector : public IConnector {
public:
	IBaseConnector(const std::string & name);
	virtual ~IBaseConnector();

	/**
	 * Get connector name for logging.
	 */
	inline const char * Name() const { return _name.c_str(); }

	/**
	 * Override default implementation of IConnector interfaces. NEVER override these your self.
	 */
	virtual void OnConnected() override { LOG_INFO("[%s]Connected...", _name.c_str()); }
	virtual void OnReceive(char * data, size_t size) override;
	virtual void OnClose(ENet::Close code) override { LOG_INFO("[%s]Lost connection.", _name.c_str()); }

	/**
	 * New interface to handle received message.
	 *
	 * \param	msg	Received message.
	 */
	virtual void OnReceive(Protocol * msg) = 0;

	/**
	 * New interface to send google::protobuf::Message.
	 *
	 * \param	pf			Protocol family.
	 * \param	msg			Real message.
	 * \param	serial		Serial number. Default is 0
	 * \param	threadsafe	Should use thread safe buffer? Default is false.
	 * \return Whether this message has been sent to server successfully?
	 */
	bool Send(int pf, const google::protobuf::Message & msg, uint32_t serial = 0, bool threadsafe = false);

	/**
	 * Cast received binary data into protobuf::Message.
	 */
	template<class T>
	T * Cast(Protocol * proto) {
		T * msg = google::protobuf::Arena::CreateMessage<T>(_arena);
		if (!msg || !msg->ParseFromArray(proto->data, proto->size - sizeof(Protocol))) {
			std::string target = T::descriptor()->full_name();
			LOG_ERR("[%s]Failed to cast message. PF : %d, Size : %d, Serial : %d, Type : %s", _name.c_str(), (int)proto->pf, (int)proto->size, (int)proto->serial, target.c_str());
			return nullptr;
		} else {
			return msg;
		}
	}

private:
	std::string	_name;
	Protocol * _current;
	char * _arena_buffer;
	google::protobuf::Arena * _arena;
};

/**
 * TCP server side base.
 */
class IBaseService : public IService {
public:
	IBaseService(const std::string & name);
	virtual ~IBaseService();

	/**
	 * Get service name.
	 */
	inline const char * Name() const { return _name.c_str(); }

	/**
	 * Override implementation of IService. NEVER override these your self.
	 */
	virtual void OnAccept(Connection * conn) override;
	virtual void OnClose(Connection * conn, ENet::Close reason) override;
	virtual void OnShutdown() override;
	virtual void OnReceive(Connection * conn, char * data, size_t size) override;

	/**
	 * New interface to handle Accept event.
	 *
	 * \param	sender	Client.
	 */
	virtual void OnAccept(Sender * sender) {}

	/**
	 * New interface to handle Close event.
	 */
	virtual void OnClose(Sender * sender, ENet::Close reason) {}

	/**
	 * New interface to handle receive data from client.
	 *
	 * \param	sender	Client.
	 * \param	msg		Received data from client.
	 */
	virtual void OnReceive(Sender * sender, Protocol * msg) = 0;

	/**
	 * Find sender.
	 *
	 * \param	id	Unique connection id of sender.
	 * \return Found client.
	 */
	Sender * Find(uint64_t id);

	/**
	 * Broadcast google::protobuf::Message to all clients.
	 *
	 * \param	pf			Protocol family.
	 * \param	msg			Real message data.
	 * \param	threadsafe	Should use thread safe buffer? Default is false.
	 */
	void Broadcast(int pf, const google::protobuf::Message & msg, bool threadsafe = false);

	/**
	 * Cast received binary data into protobuf::Message.
	 */
	template<class T>
	T * Cast(Protocol * proto) {
		T * msg = google::protobuf::Arena::CreateMessage<T>(_arena);
		if (!msg || !msg->ParseFromArray(proto->data, proto->size - sizeof(Protocol))) {
			std::string target = T::descriptor()->full_name();
			LOG_ERR("[%s]Failed to cast message. PF : %d, Size : %d, Serial : %d, Type : %s", _name.c_str(), (int)proto->pf, (int)proto->size, (int)proto->serial, target.c_str());
			return nullptr;
		} else {
			return msg;
		}
	}

private:
	std::string _name;
	Protocol * _current;
	char * _arena_buffer;
	google::protobuf::Arena * _arena;
};